﻿// Copyright © 2011 Paul Vanukoff (pvanukoff@gmail.com)

using System;
using System.Collections.Generic;
using System.IO;
using Bravura.Contracts.PluginTypes;
using Bravura.Core.Common;
using Bravura.Core.PluginLib;
using Bravura.Native.PluginBase;

namespace Bravura.Native.Plugins.Instruments
{
	/// <summary>
	/// Output channels : 2 
	/// Voices : 10
	/// 
	/// A sampler that supports mutiple loaded samples and splicing/slicing/mixing of those samples.
	/// </summary>
	public class Splicer : Instrument<Splicer.SplicerVoice>, IInstrument
	{
		#region Configuration Properties

		public string LoopDir
		{
			get;
			set;
		}

		public string[] LoopList
		{
			get;
			set;
		}

		public int LoopSampleRate
		{
			get;
			set;
		}

		public int LoopChannel
		{
			get;
			set;
		}

		public int LoopChannelCount
		{
			get;
			set;
		}

		public int LoopSkipBytes
		{
			get;
			set;
		}

		public int LoopLength
		{
			get;
			set;
		}

		public double NormalPitch
		{
			get;
			set;
		}

		public double DefaultPan
		{
			get;
			set;
		}

		#endregion

		private SplicerBufferArray _buffers = new SplicerBufferArray();

		public Splicer(IAudioPluginHost host, int sampleRate) : base(host, sampleRate, 10)
		{
			this.LoopChannel = 0;
			this.LoopChannelCount = 1;
			this.LoopSkipBytes = 0;
			this.NormalPitch = 60;
			this.DefaultPan = 0.50;
		}

		protected override void Configure(IConfigItem item)
		{
			if (item.GetName() == "LoopList")
			{
				List<IConfigItem> items = item.GetChildItems();
				LoopList = new string[items.Count];
				for (int i = 0; i < items.Count; i++)
				{
					LoopList[i] = items[i].GetValue();
				}
			}
		}

		public override void Open()
		{
			_buffers.Load(this.LoopDir, this.LoopList, this.LoopChannel, this.LoopChannelCount, this.LoopSkipBytes, this.LoopLength);
		}

		public override Splicer.SplicerVoice CreateVoice()
		{
			return new SplicerVoice(_sampleTime, 1.0 / this.AttackTime, 1.0 / this.ReleaseTime, this.Distortion,
				_buffers, this.LoopList.Length, (double)this.LoopSampleRate / (double)_sampleRate, this.NormalPitch, this.DefaultPan);
		}

		public class SplicerVoice : Voice
		{
			#region Fields

			// configuration values
			private SplicerBufferArray _buffers = null;
			private int _loopCount = 0;
			private double _ratio = 1.0;
			private double _normalPitch = 60.0;
			private double _initPan = 0.5;

			// note-on values
			private int _loopNum = 0;
			private double _loopPos = 0.0;
			private double _speed = 1.0;
			private bool _rptSet = false;
			private double _rptFrom = 0.0;
			private double _rptTo = 0.0;
			private double _rptFromIdx = 0.0;
			private double _rptToIdx = 0.0;

			// transient values
			private double _index = 0.0;

			#endregion

			internal SplicerVoice(double dt, double attRate, double relRate, double dist,
				SplicerBufferArray buffers, int loopCount, double ratio, double normalPitch, double initialPan)
				: base(dt, attRate, relRate, dist)
			{
				// initialize configuration values
				_buffers = buffers;
				_loopCount = loopCount;
				_ratio = ratio;
				_normalPitch = normalPitch;
				_initPan = initialPan;
			}

			public override void NoteOn(double sound, double pitch, double velocity)
			{
				base.NoteOn(sound, pitch, velocity);

				// initialize custom note-on values
				_loopNum = ((int)sound) % _loopCount;
				_loopPos = sound % 1.0;
				_rptSet = false;
				_mix0 = Math.Sqrt(1.0 - _initPan);
				_mix1 = Math.Sqrt(_initPan);
				_speed = _ratio * Math.Pow(2.0, (pitch - _normalPitch) / 12.0);

				// initialize transient values
				_index = 0.0;
			}

			public override void NoteControl(string name, double value)
			{
				base.NoteControl(name, value);

				if (0 == string.Compare(name, "rptFrom", true))
				{
					_rptSet = true;
					_rptFrom = value;
					_rptFromIdx = (_rptFrom - _loopPos) * _buffers.GetLength(_loopNum);
				}
				else if (0 == string.Compare(name, "rptTo", true))
				{
					_rptSet = true;
					_rptTo = value;
					_rptToIdx = (_rptTo - _loopPos) * _buffers.GetLength(_loopNum);
				}
			}

			public override void NoteOff()
			{
				base.NoteOff();
			}

			public override double[] Sample()
			{
				// get the sample
				double sc = _buffers.Sample(_loopNum, _loopPos, _index);

				// apply distortion ?
				if (_dist != 1.0)
				{
					sc = (sc < 0.0) ? -Math.Pow(-sc, _dist) : Math.Pow(sc, _dist);
				}

				// apply level
				sc *= _velocity * _level * _level;

				// update voice envelope
				UpdateEnvelope();

				// if release is done, quit moving
				if (_released) _speed = 0.0;

				// increment index
				_index += _speed;

				// wrap the index?
				if (_rptSet)
				{
					if (_index >= _rptToIdx)
					{
						_index -= (_rptToIdx - _rptFromIdx);
					}
				}

				// set sample values
				_sample[0] = _mix0 * sc;
				_sample[1] = _mix1 * sc;

				return _sample;
			}
		}
	}

	internal class SplicerBufferArray
	{
		private SplicerBuffer[] _buffers = null;
		private int _count = 0;

		public void Load(string dir, string[] files, int chan, int chanCount, int skipBytes, int length)
		{
			_count = files.Length;
			_buffers = new SplicerBuffer[_count];

			for (int i = 0; i < _count; i++)
			{
				string path = Path.Combine(dir, files[i]);
				SplicerBuffer temp = new SplicerBuffer();
				temp.LoadSample(new FileInfo(path), chan, chanCount, skipBytes, length);
				_buffers[i] = temp;
			}
		}

		public double GetLength(int loop)
		{
			return _buffers[loop].GetLength();
		}

		public double Sample(int loop, double start, double index)
		{
			return _buffers[loop].Sample(start, index);
		}
	}

	internal class SplicerBuffer : SampleBuffer
	{
		public void LoadSample(FileInfo fileinfo, int chan, int chanCount, int skipBytes, int length)
		{
			double[] rawData = Utilities.LoadSample(fileinfo, chan, chanCount, skipBytes, length);
			this.Load(rawData);
		}

		public double Sample(double start, double index)
		{
			double pos = (start * _length + index);
			if (pos >= _length)
			{
				pos = pos % _length;
			}
			return this.GetValue(pos);
		}
	}
}
